package com.google.code.hibernate.rest.impl;
import static com.google.code.hibernate.rest.internal.InternalPreconditions.checkNotNull;
import static com.google.code.hibernate.rest.method.OptionsMethodProcessor.HEADER_ALLOW;
import static java.net.HttpURLConnection.HTTP_BAD_METHOD;
import static java.net.HttpURLConnection.HTTP_BAD_REQUEST;
import static java.net.HttpURLConnection.HTTP_CONFLICT;
import static java.net.HttpURLConnection.HTTP_NOT_FOUND;

import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.hibernate.PropertyValueException;
import org.hibernate.exception.ConstraintViolationException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.code.hibernate.rest.EntityNotFoundException;
import com.google.code.hibernate.rest.ExceptionHandler;
import com.google.code.hibernate.rest.RestInterface;
import com.google.code.hibernate.rest.UnmarshalException;
import com.google.code.hibernate.rest.internal.InternalStreams;
import com.google.code.hibernate.rest.method.OptionsMethodProcessor;
import com.google.code.hibernate.rest.method.URINotMatchedException;
import com.google.code.hibernate.rest.method.UnsupportedMethodException;

/**
 * 
 * @author wangzijian
 * 
 */
public class ExceptionHandlerImpl implements ExceptionHandler {

	private static final String TEXT_PLAIN = "text/plain";

	private static final Logger LOGGER = LoggerFactory.getLogger(ExceptionHandlerImpl.class);
	
	private final Map<Class<? extends RuntimeException>, Integer> statusMap 
		= new HashMap<Class<? extends RuntimeException>, Integer>();
	
	private final OptionsMethodProcessor optionsMethodProcessor;
	
	public ExceptionHandlerImpl(OptionsMethodProcessor optionsMethodProcessor) {
		this.optionsMethodProcessor = checkNotNull(optionsMethodProcessor, "optionsMethodProcessor");
		
		map(PropertyValueException.class, HTTP_BAD_REQUEST);
		map(UnmarshalException.class, HTTP_BAD_REQUEST);
		map(IllegalEntityException.class, HTTP_BAD_REQUEST);

		map(IllegalIdException.class, HTTP_NOT_FOUND);
		map(EntityNotFoundException.class, HTTP_NOT_FOUND);
		map(URINotMatchedException.class, HTTP_NOT_FOUND);
		
		map(ConstraintViolationException.class, HTTP_CONFLICT);
		
		map(UnsupportedMethodException.class, HTTP_BAD_METHOD);
	}
	
	@Override
	public boolean canHandle(Class<? extends RuntimeException> type) {
		checkNotNull(type, "type");
		return statusMap.containsKey(type);
	}

	@Override
	public void handle(RuntimeException exception, HttpServletRequest request, HttpServletResponse response) throws IOException {
		checkNotNull(exception, "exception");
		checkNotNull(request, "request");
		checkNotNull(response, "response");
		Integer status = statusMap.get(exception.getClass());
		checkNotNull(status, "status");
		response.setStatus(status);
		String errorMessage = exception.getMessage();
		if (LOGGER.isInfoEnabled()) {
			LOGGER.info("Response error -> " + errorMessage);
		}
		if (HTTP_BAD_METHOD == status) {
			response.setHeader(HEADER_ALLOW, optionsMethodProcessor.getAllowOptions(request.getPathInfo()));
		}
		response.setContentLength(errorMessage.length());
		response.setContentType(TEXT_PLAIN);
		response.setCharacterEncoding(RestInterface.ENCODING);
		InternalStreams.write(errorMessage, response.getOutputStream());
	}

	public void map(Class<? extends RuntimeException> exception, int statusCode) {
		checkNotNull(exception, "exception");
		statusMap.put(exception, statusCode);
	}

	public Map<Class<? extends RuntimeException>, Integer> getStatusMap() {
		return Collections.unmodifiableMap(statusMap);
	}
	
	
}
